﻿using CommandLine;
using ScreenCapture.Base.Services;
using ScreenCapture.Common;
using ScreenCapture.Core;
using ScreenCapture.Core.Models;
using ScreenCapture.Loc;
using ScreenCapture.ViewCore.VmBinder;
using ScreenCapture.Views;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Threading;

namespace ScreenCapture
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public App()
        {
            SingleInstanceManager.SingleInstanceCheck();

            // Splash Screen Should be Created manually and afeter single-instance is checked
            // 启动花边必须是在单应用检测之后立即手动创建
            ShowSplashScreen();
        }

        /// <summary>
        /// 命令行参数对应的实体类,这里的null! ,意思是告诉编译器忽略可能的空引用警告.
        /// 其实下面的这行代码的初始值为null,但是告诉编译器,这个CmdOptions在使用之前我一定会赋值,
        /// 它肯定不是null,所以不需要发出可能的null引用警告.
        /// </summary>
        public static CmdOptions CmdOptions { get; private set; } = null!;


        /// <summary>
        /// 显示启动画面(初始屏幕)
        /// </summary>
        private void ShowSplashScreen()
        {
            var splashScreen = new SplashScreen("Images/Logo.png");
            // 是否自动关闭
            splashScreen.Show(true);
        }

        // UI线程上发生未处理的异常,就会触发DispatcherUnhandledException事件,然后执行这个函数.
        /*
         * 解释一下WPF中各个线程的含义:
         * 1) UI线程
         * 在WPF中UI线程就是主线程.这个线程用来处理所有的UI才做的,包括绘制界面,处理用户输入等等所有的
         * UI元素必须在UI线程上创建和访问
         * 
         * 2) 主线程
         * 在大多数的应用程序中,主线程是程序启动后创建的第一个线程,在WPF中,主线程就是UI线程
         * 
         * 3) 后台线程:
         * 后台线程就是一个在后台运行的线程,当所有的前台线程都结束的时候,后台线程会结束.在.Net中你可以设置
         * Thread.IsBackground=True设置线程为后台线程
         * 
         * 4) 前台线程:
         * 前台线程就是在前台运行的线程,前台线程可以阻止应用程序的退出. .Net中默认创建的线程都是前台线程
         * 
         */
        private void App_OnDispatcherUnhandledException(object Sender, DispatcherUnhandledExceptionEventArgs ExceptionArgs)
        {
            string exceptionLogDir = Path.Combine(ServiceProvider.SettingsDir, "Crashes");
            if (!Directory.Exists(exceptionLogDir))
            {
                Directory.CreateDirectory(exceptionLogDir);
            }
            File.WriteAllText(Path.Combine(exceptionLogDir, $"{DateTime.Now:yyyy-MM-dd-HH-mm-ss}"), ExceptionArgs.Exception.ToString());
            ExceptionArgs.Handled = true;

            new ErrorWindow(ExceptionArgs.Exception, ExceptionArgs.Exception.Message).ShowDialog();
        }

        // 程序启动的时候自动调用的方法
        private void Application_Startup(object Sender, StartupEventArgs EventArgs)
        {
            AppDomain.CurrentDomain.UnhandledException += OnCurrentDomainOnUnhandledException;
            ServiceProvider.LoadModule(new CoreModule());

            Thread thread = new Thread(() =>
            {
                try
                {
                    throw new ArgumentOutOfRangeException("内部异常");
                }
                catch (Exception ex)
                {
                    ex.Data.Add("DatabaseName", "数据库Fioman");
                    ex.Data.Add("HappenTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                    ex.HelpLink = "Https://www.baidu.com";
                    throw new InvalidOperationException("外部异常", ex);
                }
            });

            //thread.Start();  模拟异常先关闭
            // 加载主要的ViewModel模块类
            ServiceProvider.LoadModule(new ViewCoreModule());

            // 解析命令行参数,如果解析成功,执行后面的操作. 
            /*
             * 注意这里和链式调用是不太一样的,种类Parser.Default.ParseArguments<CmdOptions>(EventArgs.Args)的返回值
             * 为ParserResult<T>,而这个泛型类中定义了一系列用于处理解析结果的方法,例如WithParserd(),WinthNotParsed等.
             * 这些方法可以通过调用的方式,一步步地处理解析结果.
             * 链式调用的特点就是每个方法都会返回一个新的对象或者是只,以便我们可以继续调用下一个方法.在这个例子中,
             * ParserResult<T>.WithParsed()方法会返回一个新的ParserResult<T>对象,该对象可以继续调用其他方法.
             * WithParsed()需要传入一个委托作为参数,该委托负责处理解析成功时得到的命令行参数对象.
             */
            Parser.Default.ParseArguments<CmdOptions>(EventArgs.Args)
                .WithParsed(M => CmdOptions = M);
            if (CmdOptions?.Settings != null)
            {
                ServiceProvider.SettingsDir = CmdOptions.Settings;
            }

            var settings = ServiceProvider.Get<Settings>();

            // 加载初始化设置
            InitTheme(settings);

            // 加载初始化语言
            BindLanguageSettings(settings);

            BindKeymapSettings(settings);

        }



        // 这事件是.NET框架级别的,会捕获所有在应用程序域中没有被处理的异常.这对于一般的.NET应用程序都适用
        // 这个事件只能用来记录异常或者是做一些清理工作,因为当这事件被触发的时候,应用程序已经处于崩溃的状态,无法恢复
        // Application.Current.DispatcherUnhandledException:这个事件是WPF应用乘除级别的,只会捕获到在主UI线程上
        // 未捕获的异常.当这个事件被触发的时候,异常还没有被.NET框架捕获,所以在这个事件处理器中处理异常并防止应用程序
        // 崩溃.如果你将这个DispatcherUnhandledExceptionEventArgs的Handler属性设置为true,那么这个异常就不会再传播到
        // AppDomain.CurrentDomain.UnhandledException.
        private void OnCurrentDomainOnUnhandledException(object Sender, UnhandledExceptionEventArgs E)
        {
            string exceptionDir = Path.Combine(ServiceProvider.SettingsDir, "Crashes");
            if (!Directory.Exists(exceptionDir))
            {
                Directory.CreateDirectory(exceptionDir);
            }

            File.WriteAllText(Path.Combine(exceptionDir, $"{DateTime.Now:yyyy-MM-dd-HH-mm-ss}.txt"), E.ExceptionObject.ToString());
            // 弹窗提示
            if (E.ExceptionObject is Exception e)
            {
                Current.Dispatcher.Invoke(() => new ErrorWindow(e, e.Message).ShowDialog());
            }

            // 然后关闭当前应用
            Shutdown();
        }


        private static void InitTheme(Settings Settings)
        {
            // 如果没有复位指令
            if (!CmdOptions.Reset)
            {
                Settings.Load();
            }
        }

        private void BindLanguageSettings(Settings Settings)
        {
            var loc = LanguageManager.Instance;
            if (!string.IsNullOrWhiteSpace(Settings.UI.Language))
            {
                var matchedCulture = loc.AvailableCultures.FirstOrDefault(M => M.Name == Settings.UI.Language);
                if (matchedCulture != null)
                    loc.CurrentCulture = matchedCulture;
            }

            // 语言更改的时候触发这个事件,传入的参数是L(CultureInfo类型的,在事件触发的时候自动传入)
            // 语言更改的时候自动版更换Settings中的绑定的Language信息
            loc.LanguageChangedEvent += L => Settings.UI.Language = L.Name;
        }

        /// <summary>
        /// 先不做,回头再做,需要的时候再做.
        /// </summary>
        /// <param name="settings"></param>
        private void BindKeymapSettings(Settings settings)
        {

        }

    }
}
